
#ifndef OBJECT_H
#define OBJECT_H

#include "Type.h"
#include <assert.h>
#include <string>
#include <vector>
#include <iostream>

namespace mjc {

	//Represents an object inside the program, of the types defined in Object::Kind.
	//It stores data and properties of each of the objects found.
	class Object
	{
	public:
		enum Kind
		{
			K_CONSTANT,
			K_VARIABLE,
			K_TYPE,
			K_METHOD,
			K_PROGRAM
		};
		
		static class ConstantData
		{
			int value;
		public:
			ConstantData(): value(0) {}
			ConstantData(int val): value(val) {}
			ConstantData(const ConstantData& cd): value(cd.value) {}
			ConstantData& operator = (const ConstantData& cd)
			{
				value = cd.value;
				return *this;
			}
			int GetValue() const { return value; }
			void SetValue(int value) { this->value = value; }
		};

		static class VariableData
		{
			unsigned int address;
			unsigned int level;//scope level
		public:
			VariableData(): address(0), level(0) {}
			VariableData(unsigned int _address, unsigned int _level): address(_address), level(_level) {}
			VariableData(const VariableData& vd): address(vd.address), level(vd.level) {}
			VariableData& operator = (const VariableData& vd)
			{
				address = vd.address;
				level = vd.level;
				return *this;
			}
			unsigned int GetAddress() const { return address; }
			void SetAddress(unsigned int address) { this->address = address; }
			unsigned int GetLevel() const { return level; }
			void SetLevel(unsigned int level) { this->level = level; }
		};

		static class MethodData
		{
			unsigned int address;
			unsigned int nParameters;
			std::vector<Object*> locals;//parameters and local objects
		public:
			MethodData(): address(0), nParameters(0), locals() {}
			MethodData(const MethodData& md);
			MethodData& operator = (const MethodData& md);
			~MethodData();

			unsigned int GetAddress() const { return address; }
			void SetAddress(unsigned int address) { this->address = address; }
			unsigned int GetNParameters() const { return nParameters; }
			void SetNParameters(unsigned int nParameters) { this->nParameters = nParameters; }
			void AddLocal(const Object& local) { locals.push_back(new Object(local)); }
			Object& GetLocal(unsigned int index) const { return *(locals[index]); }
			unsigned int GetLocalsCount() const { return locals.size(); }
		};

	private:
		Kind kind;
		std::wstring name;
		Type* type;//type information
		
		//only one of these should be used
		ConstantData* constantData;
		VariableData* variableData;
		MethodData* methodData;

		Object() {} //prevent using default constructor to enforce that every object has a kind, name and type
		void CopyData(const Object& object);
		void AllocData();
		void FreeData();

	public:

		Object(const Kind& kind, const std::wstring& name, const Type& type);
		Object(const Object& other);
		Object& operator = (const Object& other);
		~Object();

		Kind GetKind() const { return kind; }
		std::wstring GetName() const { return name; }
		Type& GetType() const { return *type; }

		ConstantData& GetConstantData();
		ConstantData GetConstantData() const;
		void SetConstantData(const ConstantData& cd);
		VariableData& GetVariableData();
		VariableData GetVariableData() const;
		void SetVariableData(const VariableData& vd);
		MethodData& GetMethodData();
		MethodData GetMethodData() const;
		void SetMethodData(const MethodData& md);
		
		friend std::wostream& operator << (std::wostream& os, const Object& object);
	};
}

#endif